// TODO: CODE REVIEW & CLEANUP!
//
// WorkQueue.cs
//
// Implements the WorkQueue class and related types.
//

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace HomeUX.Utilities
{

/// <summary>
/// A queue of work items which execute at a specified time on a single worker thread.
/// </summary>
///
[DebuggerDisplay("WorkQueue {_name} - {WorkItemCount} item(s)")]
public class WorkQueue : IDisposable, IDisposedSource
#if !SILVERLIGHT
    ,ISynchronizeInvoke
#endif
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Fields
    //

    /// <summary>
    /// The thread that executes work items.
    /// </summary>
    Thread _workerThread;

    /// <summary>
    /// The first item in the work queue, or <n>null</n> if the work queue is empty.
    /// </summary>
    WorkItem _head;

    /// <summary>
    /// An <n>AutoResetEvent</n> which is set to wake the work queue thread from its sleep.  This
    /// will cause it to check the value of <n>_stopping</n> (to see if it should exit), to
    /// execute all work items whose time has come, and to re-evaluate how long it should sleep.
    /// </summary>
    AutoResetEvent _wakeWorkerThread = new AutoResetEvent(false);

#if SILVERLIGHT
    /// <summary>
    /// The fixed point in time when the work queue clock started. 
    /// </summary>
    DateTime _startTime;
#else
    /// <summary>
    /// The current time on the work queue clock, measured from a fixed point in the past.
    /// </summary>
    Stopwatch _now;
#endif

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Internal Fields
    //

    /// <summary>
    /// The name of this work queue -- also used as the name of its worker thread.
    /// </summary>
    internal string _name;

    /// <summary>
    /// This object is locked whenever the work queue is manipulated.
    /// </summary>
    internal object _workQueueLock = new object();

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    /// <summary>
    /// Gets the current time used by the work queue.
    /// </summary>
    ///
    public When Now
    {
        get
        {
#if SILVERLIGHT
            return new When((long) (DateTime.UtcNow - _startTime).TotalMilliseconds);
#else
            return new When(_now.ElapsedMilliseconds);
#endif
        }
    }

    /// <summary>
    /// Returns the count of work items in the queue, i.e. work items that were scheduled for
    /// execution but which were not yet executed or canceled.
    /// </summary>
    public int WorkItemCount { get; private set; }

    /// <summary>
    /// Returns <n>true</n> if <r>Dispose</r> was called but has not yet completed.
    /// </summary>
    public bool IsDisposing { get; private set; }

    /// <summary>
    /// Returns <n>true</n> if <r>Dispose</r> was called and completed.
    /// </summary>
    public bool IsDisposed
	{
		get
		{
			return (_workerThread == null);
		}
	}

    public bool IsWorkQueueThread
    {
        get
        {
            return (Thread.CurrentThread == _workerThread);
        }
    }

    public void AssertIsWorkQueueThread()
    {
        if (!IsWorkQueueThread)
        {
            Thread thread = Thread.CurrentThread;
            string label = (thread.Name != null) ? thread.Name :
                thread.ManagedThreadId.ToString();
            throw new InvalidOperationException(String.Format(
                "The current thread is supposed to be \"{0}\" but is actually \"{1}\"",
                _name, label));
        }
    }

    /// <summary>
    /// Returns the list of work items in the queue.  This property is O(n).  Don't use
    /// <c>WorkItems.Length</c> as a way of determining the number of items in the work queue --
    /// use <r>WorkQueue.WorkItemCount</r> instead.
    /// </summary>
    public WorkItem[] WorkItems
    {
        get
        {
            lock (_workQueueLock)
            {
                // allocate an array to store the results
                var result = new WorkItem[WorkItemCount];

                // iterate through the work item queue
                WorkItem workItem = _head;
                for (int i = 0; i < WorkItemCount; i++)
                {
                    // make sure we didn't hit the end of the list before WorkItemCount items
                    if (workItem == null)
                        throw new InternalErrorException("WorkQueue1001");

                    // copy the work item to the array to be returned
                    result[i] = workItem;

                    // check that <workItem._previous> is what we expect
                    if (i == 0)
                    {
                        if (workItem._previous != null)
                            throw new InternalErrorException("WorkQueue1002");
                    }
                    else
                    {
                        if (workItem._previous != result[i - 1])
                            throw new InternalErrorException("WorkQueue1003");
                    }

                    // go to the next work item
                    workItem = workItem._next;
                }

                // we should now be at the end of the list
                if (workItem != null)
                    throw new InternalErrorException("WorkQueue1004");

                // done
                return result;
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Events
    //

    /// <summary>
    /// Occurs when the <r>WorkQueue</r> is disposed by a call to <s>WorkQueue.Dispose</s>.
    /// </summary>
    public event Action Disposed;

    //TODO: format all this:
	public event EventHandler<ExceptionCaughtEventArgs> ExceptionCaught;

    Dictionary<Type, bool> _exceptionTypesCaught = new Dictionary<Type,bool>();

    public void CatchExceptionTypes(params Type[] types)
    {
        foreach (Type type in types)
            _exceptionTypesCaught[type] = true;
    }

    bool IsCaughtExceptionType(Type type)
    {
        bool unused;
        return _exceptionTypesCaught.TryGetValue(type, out unused);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="name">The name of this work queue -- also used as the name of its worker
    ///     thread.</param>
    ///
    public WorkQueue(string name)
    {
        _name = name;
#if SILVERLIGHT
        _startTime = DateTime.UtcNow;
#else
        _now = Stopwatch.StartNew();
#endif
        _workerThread = new Thread(WorkerThreadMethod);
        _workerThread.Name = name;
        _workerThread.Start();
    }

    /// <summary>
    /// Aborts all work items, waits for the currently-executing work item (if any) to complete,
    /// and then stops the worker thread (if it's not already stopped).  Does nothing if
    /// <r>Dispose</r> was previously called.
    /// </summary>
    ///
    public void Dispose()
    {
        // dispose of <this>
        lock (_workQueueLock)
        {
			if (!IsDisposed)
			{
				IsDisposing = true;
				while (_head != null)
					AbortWorkItem(_head);
                _wakeWorkerThread.Set();
                if (!IsWorkQueueThread)
				    _workerThread.Join();
				_workerThread = null;
				IsDisposing = false;
			}
        }

        // inform interested parties that this object is disposed
        if (Disposed != null)
            Disposed();
    }

    /// <summary>
    /// Schedules a work item to execute as soon as possible.
    /// </summary>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    /// <returns>
	/// The scheduled <r>WorkItem</r>.  This work item is guaranteed to execute on the worker
	/// thread of the work queue.  Note that it's possible that the work item has already
	/// executed by the time this method returns.
	/// </returns>
	///
    public WorkItem Add(Action action)
    {
        return Add(Now, action);
    }

    /// <summary>
    /// Schedules a work item to execute at a given time.
    /// </summary>
	///
    /// <param name="millisecondsFromNow">The time at which the work item will be scheduled to
    ///     execute, measured in milliseconds from the current time.</param>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    /// <returns>
	/// The scheduled <r>WorkItem</r>.  This work item is guaranteed to execute on the worker
	/// thread of the work queue.  Note that it's possible that the work item has already
	/// executed by the time this method returns.
	/// </returns>
	///
    public WorkItem Add(int millisecondsFromNow, Action action)
    {
        return Add(Now + millisecondsFromNow, action);
    }

    /// <summary>
    /// Schedules a work item to execute at a given time.
    /// </summary>
	///
    /// <param name="when">The time at which the work item will be scheduled to execute, based on
    ///     the clock <r>Now</r>.</param>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    /// <returns>
	/// The scheduled <r>WorkItem</r>.  This work item is guaranteed to execute on the worker
	/// thread of the work queue.  Note that it's possible that the work item has already
	/// executed by the time this method returns.
	/// </returns>
	///
    public WorkItem Add(When when, Action action)
    {
        var newWorkItem = new WorkItem(when, action);
        Add(newWorkItem);
        return newWorkItem;
    }

    public void Add(WorkItem newWorkItem)
    {
        if (newWorkItem.WorkQueue != null)
            throw new ArgumentException("WorkItem is already in a WorkQueue", "newWorkItem");
        else
            newWorkItem.WorkQueue = this;

        lock (_workQueueLock)
        {
            // set <newWorkItem> to the work item to schedule
            WorkItemCount++;

            // if the work queue is empty, <newWorkItem> becomes the sole item in it
            if (_head == null)
            {
                _head = newWorkItem;
                _wakeWorkerThread.Set();
                return;
            }

            // if <newWorkItem> should be run before the first queued work item, insert
            // <newWorkItem> at the start of the work queue
            if (newWorkItem.When < _head.When)
            {
                newWorkItem._next = _head;
                _head._previous = newWorkItem;
                _head = newWorkItem;
                _wakeWorkerThread.Set();
                return;
            }

            // set <w> to the first work item that should be run later than <newWorkItem>; if
            // none is later, append <newWorkItem> to the end of the work queue
            WorkItem w = _head;
            while (w.When <= newWorkItem.When)
            {
                if (w._next == null)
                {
                    // append <newWorkItem> to the end of the work queue (after <w>)
                    w._next = newWorkItem;
                    newWorkItem._previous = w;
                    return;
                }
				else
					w = w._next;
            }

            // insert <newWorkItem> before <w>
            newWorkItem._next = w;
            newWorkItem._previous = w._previous;
            newWorkItem._previous._next = newWorkItem;
            w._previous = newWorkItem;
        }
    }

    /// <summary>
    /// Schedules a work item to execute as soon as possible.
    /// </summary>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    /// <returns>
    /// The scheduled <r>WorkItem</r>.  This work item is guaranteed to execute on the worker
    /// thread of the work queue.  Note that it's possible that the work item has already
    /// executed by the time this method returns.
    /// </returns>
    ///
    public WorkItem InvokeAsync(Action action)
    {
        return Add(Now, action);
    }

    /// <summary>
    /// Removes from the work queue each <r>WorkItem</r> that is of type <r>WorkItemWithKey{}</r>
    /// and that has a specified key.
    /// </summary>
    ///
    /// <typeparam name="KeyType">The type of <pr>key</pr>.</typeparam>
    ///
    /// <param name="key">The key of the work item(s) to remove.</param>
    ///
    public void AbortWorkItemsWithKey<KeyType>(KeyType key)
    {
        WorkItem[] workItems = WorkItems;
        foreach (WorkItem workItem in workItems)
        {
            var workItemWithKey = workItem as WorkItemWithKey<KeyType>;
            if ((workItemWithKey != null) && Object.Equals(workItemWithKey.Key, key))
                workItemWithKey.Abort();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Internal Methods
    //

    /// <summary>
    /// Removes a given <r>WorkItem</r> from the work queue, if it's currently queued.
    /// </summary>
    ///
    /// <param name="workItem">The work item to remove.</param>
    ///
    internal void AbortWorkItem(WorkItem workItem)
    {
        lock (_workQueueLock)
        {
            // if <workItem> is at the head of the queue, remove it and update <_head>
            if (workItem == _head)
            {
                _head = _head._next;
				if (_head != null)
					_head._previous = null;
                workItem._next = null;
                // don't bother with _wakeWorkerThread.Set() -- why wake up the worker thread just
                // to tell it to reschedule when it should wake up?
                WorkItemCount--; // <workItem> was queued, now dequeued
                return;
            }

            // set <originalPrevious> to the item that precedes <workItem>, or null if none
            WorkItem originalPrevious = workItem._previous;

            // <wasQueued> will be set to true if this work item was in the queue
            bool wasQueued = false;

            // if an item precedes <workItem>, remove the links between it and <workItem>
            if (originalPrevious != null)
            {
                originalPrevious._next = workItem._next;
                workItem._previous = null;
                wasQueued = true;
            }

            // if an item follows <workItem>, remove the links between it and <workItem>
            if (workItem._next != null)
            {
                workItem._next._previous = originalPrevious;
                workItem._next = null;
                wasQueued = true;
            }

            // if <workItem> was queued, update the count of work items now that we're removing it
            if (wasQueued)
                WorkItemCount--;
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Methods
    //

    /// <summary>
    /// The method which executes the work queue's worker thread.
    /// </summary>
    ///
    void WorkerThreadMethod()
    {
        // execute work items from the work queue until it's time to quit
        while (!IsDisposing)
        {
            // set <activeWorkItem> to the WorkItem to begin executing -- or, if it's not yet time
            // to execute the next work item (or if there are no work items in the queue), set
			// <activeWorkItem> to null and <waitMilliseconds> to the number of milliseconds to
			// wait (or to -1 to wait indefinitely, if the queue is empty)
            WorkItem activeWorkItem;
            long waitMilliseconds;
            lock (_workQueueLock)
            {
                if ((activeWorkItem = _head) != null)
                {
                    // the queue is not empty
                    waitMilliseconds = activeWorkItem.When - Now;
                    if (waitMilliseconds <= 0)
                    {
                        // it's time to execute <activeWorkItem> -- remove it from the work queue
                        _head = _head._next;
                        if (_head != null)
                            _head._previous = null;
                        activeWorkItem._next = null;
                        WorkItemCount--;
                    }
                    else
					{
						// it's not yet time to execute <activeWorkItem> -- just wait until it is
                        activeWorkItem = null;
					}
                }
                else
                    waitMilliseconds = -1; // wait indefinitely
            }

            // execute <activeWorkItem> if any, otherwise wait <waitMilliseconds> milliseconds
            if (activeWorkItem != null)
			{
                try
                {
                    activeWorkItem.Action();
                }
#if !NOCATCH
                catch (Exception ex)
                {
                    if (IsCaughtExceptionType(ex.GetType()))
                    {
                        if (ExceptionCaught != null)
                        {
                            ExceptionCaught(this, new ExceptionCaughtEventArgs()
                            {
                                Exception = ex
                            });
                        }
                    }
                    else
                        throw;
                } // exception here? try defining NOCATCH
#else
                finally
                {
                    // avoid warning "The event '...ExceptionCaught' is never used"
                    if (ExceptionCaught != null)
                    {
                    }
                }
#endif
			}
            else
            {
                int millisecondsToWait = (int)Math.Min(waitMilliseconds, int.MaxValue);
#if SILVERLIGHT
                _wakeWorkerThread.WaitOne(millisecondsToWait);
#else
                _wakeWorkerThread.WaitOne(millisecondsToWait, false);
#endif
            }
        }
    }

#if !SILVERLIGHT
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // ISynchronizeInvoke Implementation
	//

    /// <summary>
    /// Asynchronously calls a given method on this work queue's worker thread.
    /// </summary>
    ///
    /// <param name="method">The method to call.</param>
    ///
    /// <param name="args">Arguments for the method.  This can be <n>null</n> if the method has no
    ///     arguments.</param>
    ///
    /// <returns>
    /// An <n>IAsyncResult</n> implementation that represents the asynchronous operation.
    /// </returns>
    ///
    public IAsyncResult BeginInvoke(Delegate method, object[] args)
    {
        var invocation = new Invocation(method, args);
        Add(Now, invocation.Invoke);
        return invocation;
    }

    /// <summary>
    /// Waits until the process started by calling <r>BeginInvoke</r> completes, and then returns
    /// the value generated by the process.
    /// </summary>
    ///
    /// <param name="asyncResult">The value returned from <r>BeginInvoke</r>.</param>
    ///
    /// <returns>
    /// The return value from the method that was called asynchronously, or <n>null</n> if no
    /// value was returned.
    /// </returns>
    ///
    public object EndInvoke(IAsyncResult asyncResult)
    {
        var invocation = asyncResult as Invocation;
        if (invocation == null)
            throw new ArgumentException("IAsyncResult is not from WorkQueue", "result");
        return invocation.Result;
    }

    /// <summary>
    /// Synchronously calls a given method on this work queue's worker thread.
    /// </summary>
    ///
    /// <param name="method">The method to call.</param>
    ///
    /// <param name="args">Arguments for the method.  This can be <n>null</n> if the method has no
    ///     arguments.</param>
    ///
    /// <returns>
    /// The return value from the method, or <n>null</n> if no value was returned.
    /// </returns>
    ///
    public object Invoke(Delegate method, object[] args)
    {
        IAsyncResult asyncResult = BeginInvoke(method, args);
        asyncResult.AsyncWaitHandle.WaitOne();
        return EndInvoke(asyncResult);
    }

    /// <summary>
    /// Always returns <n>true</n> to indicate to consumers of the <n>ISynchronizeInvoke</n>
    /// interface that <r>Invoke</r> or <r>BeginInvoke</r> must be used rather than calling
    /// methods on their own thread.
    /// </summary>
    public bool InvokeRequired
    {
        get
        {
            return true;
        }
    }
#endif

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Nested Types
    //

    /// <summary>
    /// Tracks the state of a method invocation that's performed using <n>ISynchronizeInvoke</n>
    /// methods.
    /// </summary>
    ///
    class Invocation : IAsyncResult
    {
        ///////////////////////////////////////////////////////////////////////////////////////////
        // Private Fields
        //

        /// <summary>
        /// The method to call.
        /// </summary>
        Delegate _method;

        /// <summary>
        /// The arguments for the method.  This may be <n>null</n> if there are no arguments.
        /// </summary>
        object[] _arguments;

        /// <summary>
        /// The result returned from the method being called, once it is known.  This may be
        /// <n>null</n> if there is no result.
        /// </summary>
        object _result;

        /// <summary>
        /// Set to <n>true</n> when method invocation is complete.
        /// </summary>
        bool _isComplete;

        /// <summary>
        /// The <n>WaitHandle</n> returned to callers (or <n>null</n> if none was requested) which
        /// is signalled to indicate that the method invocation is complete.
        /// </summary>
        ManualResetEvent _waitHandle;

        /// <summary>
        /// Locked when state of this object is accessed.
        /// </summary>
        object _lock = new object();

        ///////////////////////////////////////////////////////////////////////////////////////////
        // Public Properties
        //

        /// <summary>
        /// Returns the value returned from the invoked method.
        /// </summary>
        public object Result
        {
            get
            {
                lock (_lock)
                {
                    if (!_isComplete)
                        throw new InvalidOperationException("Invocation is not yet complete");
                    return _result;
                }
            }

        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        // Public Methods
        //

        /// <summary>
        /// Initializes an instance of this class.
        /// </summary>
        ///
        /// <param name="method">The method to call.</param>
        ///
        /// <param name="args">The arguments for the method.  This may be <n>null</n> if there are
        ///     no arguments.</param>
        ///
        public Invocation(Delegate method, object[] args)
        {
            _method = method;
            _arguments = args;
        }

        /// <summary>
        /// Invokes the method.
        /// </summary>
        ///
        public void Invoke()
        {
            lock (_lock)
            {
                try
                {
                    _result = _method.DynamicInvoke(_arguments);
                }
                catch (TargetInvocationException ex)
                {
                    if (ex.InnerException != null)
                    {
                        throw new InvalidOperationException(
                            String.Format("{0} in {1}.{2}: {3}",
                                ex.InnerException.GetType().Name,
                                _method.Target.ToString(),
                                _method.Method.Name,
                                ex.InnerException.Message));
                    }
                    else
                        throw;
                }
                _isComplete = true;
                if (_waitHandle != null)
                    _waitHandle.Set();
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////
        // IAsyncResult Implementation
        //

        object IAsyncResult.AsyncState
        {
            get
            {
                return null;
            }
        }

        WaitHandle IAsyncResult.AsyncWaitHandle
        {
            get
            {
                lock (_lock)
                {
                    if (_waitHandle == null)
                        _waitHandle = new ManualResetEvent(_isComplete);
                    return _waitHandle;
                }
            }
        }

        bool IAsyncResult.CompletedSynchronously
        {
            get
            {
                return false;
            }
        }

        bool IAsyncResult.IsCompleted
        {
            get
            {
                lock (_lock)
                {
                    return _isComplete;
                }
            }
        }
    }
}

/// <summary>
/// A work item within a <r>WorkQueue</r>.
/// </summary>
///
[DebuggerDisplay("WorkItem in {(WorkQueue != null) ? WorkQueue._name : \"(no queue)\"}")]
public class WorkItem
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    /// <summary>
    /// The previous work item in the work queue, or <n>null</n> if none.
    /// </summary>
    internal WorkItem _previous;

    /// <summary>
    /// The next work item in the work queue, or <n>null</n> if none.
    /// </summary>
    internal WorkItem _next;

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Properties
    //

    /// <summary>
    /// The <r>WorkQueue</r> that contains (or contained) this work item.
    /// </summary>
    public WorkQueue WorkQueue { get; internal set; }

    /// <summary>
    /// Returns <n>true</n> if this item is in its work queue, <n>false</n> if not (e.g. if it's
    /// already been executed or was canceled).
    /// </summary>
    public bool IsQueued
    {
        get
        {
            lock (WorkQueue._workQueueLock)
                return (_previous != null) || (_next != null);
        }
    }

    /// <summary>
    /// The time at which the work is to be performed
    /// </summary>
    public When When { get; private set; }

    /// <summary>
    /// The action that this work item performs.
    /// </summary>
    public Action Action { get; private set; }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="when">The time at which the work item will execute, based on the clock
    /// 	<r>WorkQueue.Now</r>.</param>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    public WorkItem(When when, Action action)
    {
        When = when;
        Action = action;
    }

    /// <summary>
    /// Aborts execution of this work item, if it has not yet executed.
    /// </summary>
    ///
    public void Abort()
	{
		WorkQueue.AbortWorkItem(this);
	}
}

/// <summary>
/// A <r>WorkItem</r> that also contains a key (arbitrary data that <r>WorkQueue</r> ignores
/// except for <r>WorkQueue.AbortWorkItemsWithKey[]</r>).
/// </summary>
///
/// <typeparam name="KeyType">The type of the key.</typeparam>
///
public class WorkItemWithKey<KeyType> : WorkItem
{
    /// <summary>
    /// Gets the key of this work item.
    /// </summary>
    public KeyType Key { get; private set; }

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="when">The time at which the work item will execute, based on the clock
    /// 	<r>WorkQueue.Now</r>.</param>
    ///
    /// <param name="key">The key of this work item.</param>
    ///
    /// <param name="action">The action to perform.</param>
    ///
    public WorkItemWithKey(When when, KeyType key, Action action)
        : base(when, action)
    {
        Key = key;
    }
}

/// <summary>
/// Represents a point in time, based on a given <n>WorkQueue.Now</n> clock.
/// </summary>
///
[DebuggerDisplay("When: {_elapsedMilliseconds}")]
public struct When
{
    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Private Fields
    //

    /// <summary>
    /// The value of <n>Stopwatch.ElapsedMilliseconds</n> of the <n>WorkQueue.Now</n> clock.
    /// </summary>
    long _elapsedMilliseconds;

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Fields
    //

    /// <summary>
    /// The value of a newly-initialized <r>When</r> object.
    /// </summary>
    public static When Zero = new When();

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Public Methods
    //

    /// <summary>
    /// Returns a new <n>When</n> value which is a given number of milliseconds later than a given
    /// <n>When</n> value.
    /// </summary>
    ///
    /// <param name="when">The given <n>When</n> value.</param>
    ///
    /// <param name="milliseconds">The number of milliseconds to add.</param>
    ///
    public static When operator +(When when, long milliseconds)
    {
        return new When(when._elapsedMilliseconds + milliseconds);
    }

    /// <summary>
    /// Returns a new <n>When</n> value which is a given <n>TimeSpan</n> later than a given
    /// <n>When</n> value.
    /// </summary>
    ///
    /// <param name="when">The given <n>When</n> value.</param>
    ///
    /// <param name="timeSpan">The <n>TimeSpan</n> to add.  Note that this is truncated to an
    ///     integer number of milliseconds.</param>
    ///
    public static When operator +(When when, TimeSpan timeSpan)
    {
        return new When(when._elapsedMilliseconds + (int)timeSpan.TotalMilliseconds);
    }

    /// <summary>
    /// Returns a new <n>When</n> value which is a given number of milliseconds earlier than a
    /// given <n>When</n> value.
    /// </summary>
    ///
    /// <param name="when">The given <n>When</n> value.</param>
    ///
    /// <param name="milliseconds">The number of milliseconds to subtract.</param>
    ///
    public static When operator -(When when, long milliseconds)
    {
        return new When(when._elapsedMilliseconds - milliseconds);
    }

    /// <summary>
    /// Returns a new <n>When</n> value which is a given <n>TimeSpan</n> earlier than a given
    /// <n>When</n> value.
    /// </summary>
    ///
    /// <param name="when">The given <n>When</n> value.</param>
    ///
    /// <param name="timeSpan">The <n>TimeSpan</n> to subtract.  Note that this is truncated to an
    ///     integer number of milliseconds.</param>
    ///
    public static When operator -(When when, TimeSpan timeSpan)
    {
        return new When(when._elapsedMilliseconds - (int)timeSpan.TotalMilliseconds);
    }

    /// <summary>
    /// Returns the number of milliseconds between two <n>When</n> values.
    /// </summary>
    ///
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static long operator -(When x, When y)
    {
        return x._elapsedMilliseconds - y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is earlier than another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator <(When x, When y)
    {
        return x._elapsedMilliseconds < y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is earlier than or equal to another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator <=(When x, When y)
    {
        return x._elapsedMilliseconds <= y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is later than or equal to another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator >=(When x, When y)
    {
        return x._elapsedMilliseconds >= y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is later than another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator >(When x, When y)
    {
        return x._elapsedMilliseconds > y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is equal to another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator ==(When x, When y)
    {
        return x._elapsedMilliseconds == y._elapsedMilliseconds;
    }

    /// <summary>
    /// Return <n>true</n> if one <n>When</n> value is not equal to another.
    /// </summary>
    /// <param name="x">The first <n>When</n> value.</param>
    ///
    /// <param name="y">The second <n>When</n> value.</param>
    ///
    public static bool operator !=(When x, When y)
    {
        return x._elapsedMilliseconds != y._elapsedMilliseconds;
    }

    /// <summary>
    /// Returns a hash code for this instance.
    /// </summary>
    ///
    public override int GetHashCode()
    {
        return _elapsedMilliseconds.GetHashCode();
    }

    /// <summary>
    /// Returns <n>true</n> if this <n>When</n> instance equals another object.
    /// </summary>
    ///
    /// <param name="obj">The object to compare with.</param>
    ///
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        else
        if (!(obj is When))
            return false;
        When other = (When)obj;
        return this == other;
    }

    /// <summary>
    /// Returns the string form of this <r>When</r> instance.
    /// </summary>
    ///
    public override string ToString()
    {
        return FormatTime(_elapsedMilliseconds);
    }

    /// <summary>
    /// Formats a time value, measured in milliseconds, as a string displaying a decimal number of
	/// seconds.  For example, time value 1500 milliseconds is represented as "1.500".
    /// </summary>
    /// 
    /// <param name="time">TODO</param>
    ///
    static public string FormatTime(long time)
    {
        return String.Format("{0:n3}", (double)time / 1000.0);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////
    // Internal Methods
    //

    /// <summary>
    /// Initializes an instance of this class.
    /// </summary>
    ///
    /// <param name="milliseconds">The value of <n>Stopwatch.ElapsedMilliseconds</n> of the
    ///     <n>WorkQueue.Now</n> clock.</param>
    ///
    internal When(long milliseconds)
    {
        _elapsedMilliseconds = milliseconds;
    }
}

/// <summary>
/// Implements a <r>Disposed</r> event which fires when this object is disposed.
/// </summary>
public interface IDisposedSource
{
    /// <summary>
    /// Occurs when this object is disposed.
    /// </summary>
    event Action Disposed;
}

public class ExceptionCaughtEventArgs : EventArgs
{
	public Exception Exception { get; internal set; }
}

}
